package com.itkjb.leisurely.center.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itkjb.leisurely.authentication.core.common.properties.SecurityProperties;
import com.itkjb.leisurely.authentication.core.common.support.AuthenticationCache;
import com.itkjb.leisurely.authentication.core.common.support.RoleAndPermissionAndDevQueryEngine;
import com.itkjb.leisurely.center.sys.model.SysUserDetail;
import com.itkjb.leisurely.center.sys.model.SysUserModel;
import com.itkjb.leisurely.center.sys.service.ISysUserService;
import com.itkjb.leisurely.core.log.annotion.LeisurelyRequestLog;
import com.itkjb.leisurely.core.base.BaseResult;
import com.itkjb.leisurely.core.log.model.LogTypeEnum;
import com.itkjb.leisurely.core.model.RequestUserInfoModel;
import com.itkjb.leisurely.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Copyright (C), 2018-2099
 * FileName: CustomAuthenticationSuccessHandler
 * @Author: lix
 * Date:     2019-05-15 09:29
 * Description: 认证成功的处理器
 * Version: V1.0.0
 */
@Slf4j
@Component
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    ThreadPoolExecutor executor =  new ThreadPoolExecutor(5, 50,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<>(),new LeisurelyDefaultThreadFactory("登陆日志更新线程"));

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    /**
     *  这里不做自动注入，使用时直接从容器中获取
     *  <p>
     *      原因在于这里自动注入时，会先去初始化
     *      org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration；
     *      初始化这个bean 时 ，导致@EnableAuthorizationServer 修饰的类中的
     *      com.hirong.authentication.center.config.authentication.AuthorizationServerConfiguration#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer)
     *      方法被调用，此时该类中所有的 @Autowired 的值全部是null， 导致jwt 注入失效
     *      ##重点是服务会在注入null后再去加载jwt配置##，导致顺序错乱，不管用什么方式调整配置的加载顺序都不行##
     *  </p>
     */
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Autowired
    TokenStore tokenStore;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    SecurityProperties securityProperties;

    @Autowired
    AuthenticationCache authenticationCache;

    @Autowired
    RoleAndPermissionAndDevQueryEngine roleAndPermissionAndDevQueryEngine;

    @Autowired
    ISysUserService userService;


    /**
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.authentication
     * #onAuthenticationSuccess(javax.servlet.http.
     * HttpServletRequest, javax.servlet.http.HttpServletResponse,
     * org.springframework.security.core.Authentication)
     */
    @Override
    @LeisurelyRequestLog(value = "登录成功",type = LogTypeEnum.LOGIN)
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        // 设置登陆者到主线程中
        String username = authentication.getName();
        RequestUserInfoModel userInfoModel =new RequestUserInfoModel(username);
        RequestUserInfoUtil.setRequestUserInfoModel(userInfoModel);

        // 获取请求头中的Authorization
        String header = request.getHeader("Authorization");
        // 是否以Basic开头
        if (header == null || !header.startsWith("Basic ")) {
            // throw new UnapprovedClientAuthenticationException("请求头中无client信息");
            // 如果不是app 或者 client 端登录

            boolean bln = HttpRequestUtil.isAjaxRequest(request);
            log.debug("登录成功,请求方式{}", bln ? "json" : "html");
            if (bln) {
                //返回结果
                OAuth2AccessToken toke = createToke(securityProperties.getClient().getClientId(), securityProperties.getClient().getClientSecurity(), authentication);
                updateLastLoginInfo(authentication, request);
                writeResult(response,toke);
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }
        } else {
            String[] tokens = extractAndDecodeHeader(header, request);
            assert tokens.length == 2;
            String clientId = tokens[0];
            String clientSecret = tokens[1];
            // 生成token
            OAuth2AccessToken toke = createToke(clientId, clientSecret, authentication);
            updateLastLoginInfo(authentication, request);
            writeResult(response,toke);
        }


    }

    /**
     * 更新用户最后一次登录信息
     * @param authentication
     * @param request
     */
    private  void  updateLastLoginInfo(Authentication authentication,HttpServletRequest request){
        executor.execute(()->{
            SysUserModel userModel = new SysUserModel();
            SysUserDetail sysUserDetail = (SysUserDetail) authentication.getPrincipal();
            userModel.setId( sysUserDetail.getUserId().toString());
            userModel.setLastIp(IpAddressUtil.getIpAddress(request));
            userModel.setLastIpDes(IpAddressUtil.getHttpIPCityInfo(userModel.getLastIp()));
            this.userService.updateLastLoginInfo(userModel);
        });

    }
    /**
     * 解析请求头拿到clientid  client secret的数组
     *
     * @param header
     * @param request
     * @return
     * @throws IOException
     */
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = java.util.Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[]{token.substring(0, delim), token.substring(delim + 1)};
    }

    /**
     *  产生token
     * @author xiaoyao
     * @date  2019-05-28 10:51
     * @param clientId
     * @param clientSecret
     * @param authentication
     * @return org.springframework.security.oauth2.common.OAuth2AccessToken
     * @since V1.0.0
     **/
    private OAuth2AccessToken createToke(String clientId, String clientSecret, Authentication authentication){
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!passwordEncoder.matches(clientSecret,clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }
        TokenRequest tokenRequest = new TokenRequest(new HashMap<>(8),
                clientId,clientDetails.getScope(),"itkjb");
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,
                authentication);
        authorizationServerTokenServices = SpringBeanUtil.getBean(AuthorizationServerTokenServices.class);
        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
        SysUserDetail sysUserDetail = (SysUserDetail) authentication.getPrincipal();
        // 获取 并向缓存中 设置权限
        setTokenAndPermissionsToCatch(token, sysUserDetail);
        return token;
    }

    /**
     * 设置权限到缓存器 中
     * @param token
     * @param sysUserDetail
     */
    private void setTokenAndPermissionsToCatch(OAuth2AccessToken token, SysUserDetail sysUserDetail) {
        if(authenticationCache != null){
            String tokekey = Md5Utils.getMD5(token.getValue());
            RequestUserInfoModel infoModel = new RequestUserInfoModel(sysUserDetail.getUsername());
            infoModel.setUserId(sysUserDetail.getUserId().toString());
            List<String> roleCodes = roleAndPermissionAndDevQueryEngine.getRolesCode(sysUserDetail.getUserId().toString());
            List<String> permissions = roleAndPermissionAndDevQueryEngine.getPermissionCodes(sysUserDetail.getUserId().toString());
            List<String> depIds = roleAndPermissionAndDevQueryEngine.getDepIds(sysUserDetail.getUserId().toString());
            // 还需要补充信息
            //
            authenticationCache.setAuthenticationInfo(tokekey,infoModel,roleCodes,permissions,depIds, token.getExpiresIn());
        }
    }


    /**
     * 输出结果
     * @param response
     * @param result
     * @throws Exception
     */
    private void writeResult(HttpServletResponse response,Object result) throws IOException{
        BaseResult<Object> res = new BaseResult<>(true);
        res.setData(result);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(res));
    }

}
